package de.tmahr.android.animation;

import java.util.concurrent.LinkedBlockingQueue;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import de.tmahr.android.animation.RenderView.Animation;
import de.tmahr.android.logger.Logger;

public class Diagramm implements Animation
{
	public static class Abtastung
	{
		public Abtastung(long zeitMillisekunden, float wert)
		{
			this.zeitMillisekunden = zeitMillisekunden;
			this.wert = wert;
		}

		long zeitMillisekunden;
		float wert;
	}

	private static class AchsenFormat
	{
		float anfang;
		float ende;
		float abstand;
		int anzahl;

		/**
		 * Diese Funktion modifiziert das Intervall [min,max] zu
		 * [anfang,ende] mit der Schrittweite abstand, so daß eine für das
		 * Auge angenehme Skala entsteht.
		 */
		void formatiereAchse(float min, float max)
		{
			if (max <= min)
			{
				anfang = max;
				ende = max;
				abstand = 0;
				anzahl = 0;
			}
			else if (min == 0 && max == (int) max
					&& istZweierPotenz((int) max))
			{
				anfang = min;
				ende = max;
				abstand = max / 4;
				anzahl = 4;
			}
			else
			{
				float orderOfMagnitude = (float) liefereGroessenordnung(max
						- min);

				final int SIZE = 3;
				int fVec[] = { 1, 2, 5 };
				float startVec[] = { 0, 0, 0 };
				float endVec[] = { 0, 0, 0 };
				float deltaVec[] = { 0, 0, 0 };
				int countVec[] = { 0, 0, 0 };

				int bestIndex = 0;
				int bestDiffCountToFive = java.lang.Integer.MAX_VALUE;

				for (int i = 0; i < SIZE; i++)
				{
					float f = orderOfMagnitude / fVec[i];
					abstand = f;

					int scale = 1;
					if (f < 1)
					{
						scale = (int) (fVec[i] / orderOfMagnitude);
						min *= scale;
						max *= scale;
						f *= scale;
					}

					if (f >= 1)
					{
						anfang = f * ((int) min / (int) f);

						if (min < 0)
						{
							if (anfang != min)
							{
								anfang = f * ((int) min / (int) f - 1);
							}
						}

						ende = f * ((int) max / (int) f);
						if (max >= 0)
						{
							if (ende != max)
							{
								ende = f * ((int) max / (int) f + 1);
							}
						}

						anzahl = (int) ((ende - anfang) / f);

						if (scale != 1)
						{
							min /= scale;
							max /= scale;
							anfang /= scale;
							ende /= scale;
							f /= scale;
						}

						int diff = anzahl - 5;
						if (diff < 0)
						{
							diff = -diff;
						}
						if (diff < bestDiffCountToFive)
						{
							bestDiffCountToFive = diff;
							bestIndex = i;
						}

						startVec[i] = anfang;
						endVec[i] = ende;
						deltaVec[i] = abstand;
						countVec[i] = anzahl;
					}
				}

				anfang = startVec[bestIndex];
				ende = endVec[bestIndex];
				abstand = deltaVec[bestIndex];
				anzahl = countVec[bestIndex];
			}
		}

		/**
		 * Prüft, ob eine Zahl eine Zweierpotenz ist.
		 * 
		 * @param w
		 *            zu überprüfende Zahl
		 * @return <tt>true</tt> falls die Zahl eine Zweierpotenz ist.
		 */
		private static boolean istZweierPotenz(int w)
		{
			if (w < 2)
			{
				return false;
			}

			int v;
			while (w > 2)
			{
				v = w;
				w >>= 1;
				if (v != (w << 1))
				{
					return false;
				}
			}

			return true;
		}

		/**
		 * Ermittelt die Größenordnung einer Zahl.
		 * <p>
		 * Beispiel:
		 * <p>
		 * 12.3 -> 10
		 * <p>
		 * 2.1 -> 1
		 * <p>
		 * 0 -> 0
		 * <p>
		 * 0.2 -> 0.1
		 * <p>
		 * 
		 * @param wert
		 *            zu untersuchende Zahl
		 * @return Größenordnung der Zahl
		 */
		private static float liefereGroessenordnung(float wert)
		{
			float ordnung = 1;

			if (wert == 0)
			{
				return 0;
			}
			if (wert < 0)
			{
				wert = -wert;
			}

			if (wert > 1)
			{
				for (;;)
				{
					wert /= 10;
					if (wert >= 1)
					{
						ordnung *= 10;
					}
					else
					{
						return ordnung;
					}
				}
			}
			else
			{
				for (;;)
				{
					wert *= 10;
					if (wert < 1)
					{
						ordnung /= 10;
					}
					else
					{
						return ordnung / 10;
					}
				}
			}
		}
	}

	private LinkedBlockingQueue<Abtastung> abtastSpeicher;
	private long historieMillisekunden;
	private float minAbtastwert, maxAbtastwert;
	private long zeitKalibration;
	private AchsenFormat yAchse;
	private int farbeHintergrund;
	private int farbeVordergrund;
	private Paint paint = new Paint();
	private Rect hilfsRechteck = new Rect();
	private Logger logger;

	public Diagramm(Logger logger, long historieMillisekunden)
	{
		this.logger = logger;
		this.historieMillisekunden = historieMillisekunden;
		abtastSpeicher = new LinkedBlockingQueue<Abtastung>();
		farbeHintergrund = Color.rgb(0, 0, 0);
		setTextColor();
		yAchse = new AchsenFormat();
	}

	@Override
	public void render(Canvas canvas)
	{
		long zeitMillisekunden = System.currentTimeMillis();

		while (!abtastSpeicher.isEmpty()
				&& abtastSpeicher.peek().zeitMillisekunden
						+ zeitKalibration < zeitMillisekunden
						- historieMillisekunden)
		{
			abtastSpeicher.poll();
		}

		paint.setStyle(Style.FILL);
		paint.setColor(farbeVordergrund);
		canvas.drawColor(farbeHintergrund);

		// Zeichne und beschrifte y-Achse:
		for (int i = 0; i < yAchse.anzahl; i++)
		{
			// Zeichne y-Achse
			float y = yAchse.anfang + i * yAchse.abstand;
			int yPixel = konvertiereAbtastwertInPixel(canvas, y);
			canvas.drawLine(0, yPixel, canvas.getWidth(), yPixel, paint);

			// Beschrifte y-Achse
			String s = String.valueOf(y);
			paint.getTextBounds(s, 0, s.length(), hilfsRechteck);
			canvas.drawText(s, 0, yPixel, paint);
		}

		// Zeichne Abtastwerte
		for (Abtastung s : abtastSpeicher)
		{
			int x = konvertiereAbtastzeitInPixel(canvas, zeitMillisekunden - s.zeitMillisekunden - zeitKalibration);
			int y = konvertiereAbtastwertInPixel(canvas, s.wert);
			canvas.drawCircle(x, y, 4, paint);
		}
	}

	private int konvertiereAbtastwertInPixel(Canvas canvas, float v)
	{
		return (int) ((canvas.getHeight() - 1) * (yAchse.ende - v) / (yAchse.ende - yAchse.anfang));
	}

	private int konvertiereAbtastzeitInPixel(Canvas canvas, float v)
	{
		return (int) (canvas.getWidth() * v / historieMillisekunden);
	}

	@Override
	public void setBackgroundColor(int c)
	{
		farbeHintergrund = c;
		setTextColor();
	}

	private void setTextColor()
	{
		farbeVordergrund = Color.rgb(255 - Color.red(farbeHintergrund),
				255 - Color.green(farbeHintergrund),
				255 - Color.blue(farbeHintergrund));
	}

	public void fuegeAbtastwertHinzu(Abtastung abtastwert)
	{
		zeitKalibration = System.currentTimeMillis() - abtastwert.zeitMillisekunden;
		abtastSpeicher.add(abtastwert);

		// Überprüfe ob der neue Abtastwert kleiner oder größer als der
		// bisher kleinste bzw. größte Abtastwert ist:
		if (abtastwert.wert < minAbtastwert || abtastwert.wert > maxAbtastwert)
		{
			if (abtastwert.wert < minAbtastwert)
			{
				minAbtastwert = abtastwert.wert;
			}
			if (abtastwert.wert > maxAbtastwert)
			{
				maxAbtastwert = abtastwert.wert;
			}
			// Formatiere die y-Achse neu:
			yAchse.formatiereAchse(minAbtastwert, maxAbtastwert);
		}

		// Gebe den Diagramminhalt im Logging-Fenster aus:
		if(logger!=null)
		{
			logger.clearLog();
			logger.log("" + zeitKalibration + "\n");
			for (Abtastung s : abtastSpeicher)
			{
				long time = abtastwert.zeitMillisekunden;
				logger.log("\n" + time + " " + s.wert);
			}
		}
	}
}